#include <Windows.h>
#include <iostream>
#include <sstream>
#include <time.h>
#include "BigInt.h"
using namespace std;
using namespace APIAL;

const string TESTFILE = "BigIntTest.ini";
const int MAX_VALUE_LENGTH = 16000;
const int MAX_STRING_LENGTH = 100;
const int BUFFER_SIZE_LARGE = 16000;
const int BUFFER_SIZE_SMALL = 100;
const int MAX_NUMBER_TESTS = 1000;

const int OP_ADD = 1;
const int OP_SUBTRACT = 2;
const int OP_MULTIPLY = 3;
const int OP_DIVIDE = 4;
const int OP_LEFT_SHIFT = 5;
const int OP_RIGHT_SHIFT = 6;
const int OP_AND = 7;
const int OP_OR = 8;
const int OP_XOR = 9;
const int OP_NOT = 10;
const int OP_EQUAL = 11;

const string OP_NAME[] = { "INVALID", "ADD", "SUBTRACT", "MULTIPLY", "DIVIDE",
						   "LEFT SHIFT", "RIGHT SHIFT", "AND", "OR", "XOR", "NOT", "EQUAL" };

void RunTestFile(string pathToExec)
{
	string strPathToTestFile;

	if (pathToExec.find_last_of('\\') == string::npos)
	{
		// executable was called from within same directory
		strPathToTestFile = ".\\" + TESTFILE;
	}
	else
	{
		// extract path to executable and append file name
		strPathToTestFile = pathToExec.substr(0,pathToExec.find_last_of('\\') + 1) + TESTFILE;
	}
	const char* pathToTestFile = strPathToTestFile.c_str();

	int number_tests = GetPrivateProfileInt("INITIALIZATION", "number of tests", 0, pathToTestFile);

	if (number_tests > MAX_NUMBER_TESTS)
	{
		number_tests = MAX_NUMBER_TESTS;
	}
	
	for (int i = 1; i <= number_tests; i++)
	{
		bool binEnc = false;
		bool hexEnc = false;

		bool positive1 = false;
		bool positive2 = false;
		bool positiveExpected = false;

		// get test name
		stringstream ssTestName;
		ssTestName << "TEST" << i;
		string strTestName = ssTestName.str();
		const char* testname = strTestName.c_str();

		cout << "Test:\t" << testname << endl;
		
		char* buffer_small = new char[BUFFER_SIZE_SMALL];
		char* buffer_large = new char[BUFFER_SIZE_LARGE];

		if (0 == GetPrivateProfileSection(testname, buffer_small, BUFFER_SIZE_SMALL, pathToTestFile))
		{
			// section with testname does not exist
			cout << "Error - Test does not exist" << endl;
		}
		else
		{
			// get test information from section
			int operation = GetPrivateProfileInt(testname, "operation", 0, pathToTestFile);
			int threads = GetPrivateProfileInt(testname, "threads", 0, pathToTestFile);
			int shift_amt = GetPrivateProfileInt(testname, "shift amount", -1, pathToTestFile);

			GetPrivateProfileString(testname, "encoding", "", buffer_small, BUFFER_SIZE_SMALL, pathToTestFile);
			string encoding(buffer_small);

			GetPrivateProfileString(testname, "value1", "", buffer_large, BUFFER_SIZE_LARGE, pathToTestFile);
			string value1(buffer_large);
			GetPrivateProfileString(testname, "value2", "", buffer_large, BUFFER_SIZE_LARGE, pathToTestFile);
			string value2(buffer_large);
			GetPrivateProfileString(testname, "expected value", "", buffer_large, BUFFER_SIZE_LARGE, pathToTestFile);
			string expectedValue(buffer_large);

			GetPrivateProfileString(testname, "sign1", "", buffer_small, BUFFER_SIZE_SMALL, pathToTestFile);
			string sign1(buffer_small);
			GetPrivateProfileString(testname, "sign2", "", buffer_small, BUFFER_SIZE_SMALL, pathToTestFile);
			string sign2(buffer_small);
			GetPrivateProfileString(testname, "expected sign", "", buffer_small, BUFFER_SIZE_SMALL, pathToTestFile);
			string expectedSign(buffer_small);
		
			if (operation < OP_ADD || operation > OP_EQUAL)
			{
				// invalid operation code
				cout << "Error - Invalid operation code: " << operation << endl;
			}
			if (threads < 1)
			{
				// invalid operation code
				cout << "Error - Invalid number of threads, must be positive value" << endl;
			}
			else if ((operation == OP_LEFT_SHIFT || operation == OP_RIGHT_SHIFT) && shift_amt < 0)
			{
				// operation is shift and incorrect shift amount provided
				cout << "Error - Shift amount must be positive" << endl;
			}
			else if (operation == OP_EQUAL && (0 != _stricmp("FALSE",expectedValue.c_str())) &&
				     (0 != _stricmp("TRUE",expectedValue.c_str())))
			{
				// operation is equal and requires valid expected value
				cout << "Error - Expected value must be \"TRUE\" or \"FALSE\" for Equals operation" << endl;
			}
			else
			{
				binEnc = (0 == _stricmp("binary",encoding.c_str()));
				hexEnc = (0 == _stricmp("hexadecimal",encoding.c_str()));

				positive1 = ("+" == sign1);
				positive2 = ("+" == sign2);
				positiveExpected = ("+" == expectedSign);

				if (!binEnc && !hexEnc)
				{
					cout << "Error - Encoding must be \"binary\" or \"hexadecimal\"" << endl;
				}
				else if (!positive1 && ("-" != sign1))
				{
					cout << "Error - Sign1 must be \"+\" or \"-\"" << endl;
				}
				else if (!positive2 && ("-" != sign2) && operation != OP_RIGHT_SHIFT &&
						 operation != OP_LEFT_SHIFT && operation != OP_NOT)
				{
					// operation requires valid sign2
					cout << "Error - Sign2 must be \"+\" or \"-\"" << endl;
				}
				else if (OP_EQUAL != operation && !positiveExpected && ("-" != expectedSign))
				{
					cout << "Error - Expected Sign must be \"+\" or \"-\"" << endl;
				}
				else
				{
					BigInt bi1(4);
					BigInt bi2(4);

					int parseResult1 = 0;
					int parseResult2 = 0;

					if (binEnc)
					{
						parseResult1 = bi1.ParseBinaryString(value1, positive1);
						parseResult2 = bi2.ParseBinaryString(value2, positive2);
					}
					else // hex encoding
					{
						parseResult1 = bi1.ParseHexadecimalString(value1, positive1);
						parseResult2 = bi2.ParseHexadecimalString(value2, positive2);
					}

					if (parseResult1 != 0)
					{
						cout << "Error - Value1 did not parse successfully" << endl;
					}
					else if (parseResult2 != 0 && operation != OP_RIGHT_SHIFT &&
						     operation != OP_LEFT_SHIFT && operation != OP_NOT)
					{
						// operation requires value2 be parsed correctly
						cout << "Error - Value2 did not parse successfully" << endl;
					}
					else
					{
						omp_set_num_threads(threads);

						BigInt result(4);
						BigInt scratchspace(4);

						bool equal;

						clock_t function_called;
						clock_t function_returned;
						clock_t bi_normalized;

						function_called = clock();

						switch(operation)
						{
							case OP_ADD:
								Add(&bi1,&bi2,&result,&scratchspace);
								break;
							case OP_SUBTRACT:
								Subtract(&bi1,&bi2,&result,&scratchspace);
								break;
							case OP_MULTIPLY:
								Multiply(&bi1,&bi2,&result);
								break;
							case OP_DIVIDE:
								Divide(&bi1,&bi2,&result);
								break;
							case OP_LEFT_SHIFT:
								LeftShift(&bi1,shift_amt,&result);
								break;
							case OP_RIGHT_SHIFT:
								RightShift(&bi1,shift_amt,&result);
								break;
							case OP_AND:
								And(&bi1,&bi2,&result);
								break;
							case OP_OR:
								Or(&bi1,&bi2,&result);
								break;
							case OP_XOR:
								Xor(&bi1,&bi2,&result);
								break;
							case OP_NOT:
								Not(&bi1,&result);
								break;
							case OP_EQUAL:
								equal = Equal(&bi1,&bi2);
								break;
							default:
								break;
						}
						
						function_returned = clock();

						BigInt::normalize(result);
						bi_normalized = clock();

						cout << "Method:\t" << OP_NAME[operation] << endl;
						cout << "Threads: " << threads << endl;
						cout << "Value1:\t" << value1 << endl;
						cout << "Sign1:\t" << sign1 << endl;
						if (operation != OP_RIGHT_SHIFT && operation != OP_LEFT_SHIFT && operation != OP_NOT)
						{
							// operation requires value2
							cout << "Value2:\t" << value2 << endl;
							cout << "Sign2:\t" << sign2 << endl;
						}
						if (operation == OP_RIGHT_SHIFT || operation == OP_LEFT_SHIFT)
						{
							cout << "Shift:\t" << shift_amt << endl;
						}

						string strResult;
						string resultValue;
						string resultSign;
						if (operation == OP_EQUAL)
						{
							equal ? strResult = "TRUE" : strResult = "FALSE";
						}
						else
						{
							if (binEnc)
							{
								resultValue = PrintMagnitudeBinary(&result);
							}
							else // hex encoding
							{
								resultValue = PrintMagnitudeHexadecimal(&result);
							}

							IsPositive(&result) ? resultSign = "+" : resultSign = "-";

							strResult = resultSign + resultValue;
						}

						bool matches;
						if (operation == OP_EQUAL)
						{
							matches = (0 == _stricmp(expectedValue.c_str(),strResult.c_str()));
						}
						else
						{
							matches = (0 == _stricmp(expectedValue.c_str(),resultValue.c_str())) 
								      && (0 == _stricmp(expectedSign.c_str(),resultSign.c_str()));
						}

						string strMatches;
						matches ? strMatches = "TRUE" : strMatches = "FALSE";

						cout << "Result:\t" << strResult << endl;
						cout << "Result Matches Expected Value?\t" << strMatches << endl;

						cout << "Time to Complete Operation:\t" << function_returned - function_called << " ms" << endl;
						cout << "Time to Normalize Result:\t" << bi_normalized - function_returned << " ms" << endl;
					}
				}
			}
		}

		cout << endl << "------------------------------------------------" << endl << endl;

		if (buffer_small)
		{
			delete buffer_small;
		}
		if (buffer_large)
		{
			delete buffer_large;
		}
	}
}